﻿using System;
using System.Collections.Generic;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.MachineLearning
{
	/// <summary>
	/// Class to implement 'classic' variant of bagging with bootstrap training and voting.
	/// </summary>
	[Serializable]
	public class Bagging : IClassifier
	{
		#region - Public properties. -
		public List<IClassifier> Classifiers { get; set; }

		public bool UseVotes { get; set; }
		#endregion

		#region - Construction. -
		public Bagging ()
		{
			Classifiers = new List<IClassifier>();
			UseVotes = false;
		}

		public Bagging(Bagging bag)
		{
			Classifiers = new List<IClassifier>(bag.Classifiers);
			UseVotes = bag.UseVotes;
		}
		#endregion

		#region - [IClassifier] imlpementation. -
		public void Train(List<TrainingSample> trainData)
		{
			Train(trainData, Classifiers);
		}

		public int Recognize(float[,] obj)
		{
			return Recognize(obj, Classifiers, UseVotes);
		}

		public Dictionary<int, int> GetClassVotes(float[,] obj)
		{
			return GetClassVotes(obj, Classifiers);
		}

		public Dictionary<int, float> GetClassProbabilities(float[,] obj)
		{
			return GetClassProbabilities(obj, Classifiers);
		}

		public object Clone() { return new Bagging(this); }
		#endregion

		#region - Training. -
		/// <summary>
		/// Traing given set of classifiers using bootstrapping.
		/// </summary>
		/// <param name="data">Training data.</param>
		/// <param name="clrs">List of classifiers to train.</param>
		public void Train (List<TrainingSample> data, List<IClassifier> clrs)
		{
			Performance.MachineLearning.TrainBagging(data, clrs);
		}
		#endregion

		#region - Recognition. -
		/// <summary>
		/// Recognize given object using given set of classifiers.
		/// </summary>
		/// <param name="obj">Object description.</param>
		/// <param name="clrs">List of classifiers.</param>
		/// <param name="useVotes">Defines whether to use votes or class probabilities for recognition.</param>
		/// <returns>Class ID.</returns>
		public int Recognize(float[,] obj, List<IClassifier> clrs, bool useVotes)
		{
			if (useVotes)
			{
				var votes = GetClassVotes(obj, clrs);
				return MachineLearningElements.GetMaxClassId(votes, -1);
			}
			else
			{
				var probs = GetClassProbabilities(obj, clrs);
				return MachineLearningElements.GetMaxClassId(probs, -1);
			}
		}

		/// <summary>
		/// Get results of class voting as sum of votes obtained from each classifier.
		/// </summary>
		/// <param name="obj"></param>
		/// <param name="clrs"></param>
		/// <returns></returns>
		public Dictionary<int, int> GetClassVotes(float[,] obj, List<IClassifier> clrs)
		{
			var res = new Dictionary<int, int>();
			int count = clrs.Count;
			for (int i = 0; i < count; i++)
			{
				var temp = clrs[i].GetClassVotes(obj);
				StructMath.Accumulate(ref res, temp);
			}
			return res;
		}

		/// <summary>
		/// Get sum of class probabilities obtained from each classifier.
		/// </summary>
		/// <param name="obj"></param>
		/// <param name="clrs"></param>
		/// <returns></returns>
		public Dictionary<int, float> GetClassProbabilities(float[,] obj, List<IClassifier> clrs)
		{
			var res = new Dictionary<int, float>();
			int count = clrs.Count;
			for (int i = 0; i < count; i++)
			{
				var temp = clrs[i].GetClassProbabilities(obj);
				StructMath.Accumulate(ref res, temp);
			}
			return res;
		}
		#endregion
	}
}
